/*
 * ExtensibleHash.cpp
 *
 *  Created on: Oct 7, 2011
 *      Author: kimi
 */
#include "ExtensibleHash.h"

//// FUNCIONES DE LOG
void ExtensibleHash::logTableSize() {
	stringstream ss;
	ss.clear();
	ss << "Tamaño de HashTable. Tamaño actual:";
	char convertedDec[32];
	sprintf(convertedDec,"%lu",hashTable->getTableSize());
	ss <<  convertedDec;
	ss << ". Bit(s) usados en HashTable: ";
	sprintf(convertedDec,"%i",bitsUsedInHash);
	ss <<  convertedDec;
	log->info("table",ss.str());
}

void ExtensibleHash::logOperationWithKey(string op,unsigned long key) {
	stringstream ss;
	ss.clear();
	ss << "Operacion: " << op << ". Clave: ";
	char convertedDec[32];
	sprintf(convertedDec,"%lu",key);
	ss <<  convertedDec;
	log->info(op,ss.str());
}

void ExtensibleHash::logHashRecords() {

	for (unsigned long i = 0; i < hashRecords->size(); i++) {

		unsigned long recordsCount = 0;
		Block* block = NULL;
		if (hashRecords->getBlockByBlockId(i,&block) == true)  {

			stringstream ss;
			ss.clear();
			char convertedDec[32];
			sprintf(convertedDec,"%lu",i);
			ss << "Bloque: " << convertedDec << ". Claves: ";

			for (unsigned int j = 0; j < block->getRecordCount(); j++) {
				// recorro los registros, y obtengo el ID
				char*record = NULL;
				unsigned long recordSize = 0;
				unsigned long recordKey = 0;
				if (block->getRecord(j,&record,&recordSize)) {
					char*p = reinterpret_cast<char*>(&recordKey);
					for (unsigned long k = 0 ; k < sizeof(unsigned long); k++) {
						*p = record[k];
						p++;
					}

					sprintf(convertedDec,"%lu",recordKey);
					ss << convertedDec;
				}
				else {
					ss << " - ";
				}
				ss <<  " ; ";
			}

			log->info("block",ss.str());
		}
	}
}

void ExtensibleHash::logHashTable() {
	stringstream ss;
	ss.clear();
	ss << "HashTable: ";
	for (unsigned int i = 0; i < hashTable->getTableSize(); i++) {
		char* entry = NULL;
		unsigned long recordNumber,hashSize;
		bool res = hashTable->getEntry(i,&recordNumber,&hashSize);

		if (res == true) {
			char convertedDec[32];
			sprintf(convertedDec,"%lu",recordNumber);
			ss << convertedDec;
			ss << "[td=";
			sprintf(convertedDec,"%lu",hashSize);
			ss << convertedDec;
			ss << "]";
		}
		else {
			ss << "-[td=-]";
		}
		if (i != hashTable->getTableSize()-1) {
			ss << " ; ";
		}
	}
	log->info("table",ss.str());
}

//// END FUNCIONES DE LOG

bool ExtensibleHash::getHashTableEntry(unsigned long hash, unsigned long* blockNumber,
		unsigned long* hashSize) {

	// busco en la HashTable el registro con numero 'hash'
	bool found = hashTable->getEntry(hash,blockNumber,hashSize);

	return found;
}

bool ExtensibleHash::put(unsigned long key, char* data, unsigned long data_len) {
	unsigned long hash = hashFunction(key);

	unsigned long blockNumber = 0;
	unsigned long hashSize = 0;

	bool res = getHashTableEntry(hash,&blockNumber,&hashSize);
	if (res == true) {

		if (has(key)) {
			if (hashRecords->removeRecord(blockNumber,key) == true) {
	//			cout << "registro borrado para actualizacion" <<endl;
			}
			else {
				cerr << "<<ERROR>> No se ha borrado el registro para actualizacion" <<endl;
			}
		}

		logOperationWithKey("new",key);

//		cout << filePath << " >> hashSize: " << hashSize << " :: TSize: " << hashTable->getTableSize() << endl;

		while (hashRecords->addRecord(blockNumber,key,data,data_len) == false) {
			// aca si agarrate Cata
			/// guardo ref. a este blockNumber (hay que re acomodar estos items)
			unsigned long oldBlockNumber = blockNumber;
			unsigned long newBlockNumber = -1;

			// Verifico el tamaño de dispersión

			if (hashSize < hashTable->getTableSize()) {
				if (hashRecords->getNewBlock(&newBlockNumber) == false) {
					cerr << "<<ERROR>> Error, no se pudo obtener un nuevo bloque" << endl;
				}
				unsigned long newHashSize = hashSize*2;

				// modificar entrada que a donde apunta el nuevo bloque
				unsigned long cant = hashTable->getTableSize()/newHashSize;
				for (unsigned int i = 0; i < cant; i++) {
					unsigned long posToUpdate = (hash+newHashSize*i)%hashTable->getTableSize();
					hashTable->editEntry(posToUpdate,newBlockNumber,newHashSize);
				}


				// modificar el TD del bloque al que apuntaba anteriormente
				for (unsigned int i = 0; i < hashTable->getTableSize(); i++) {
					unsigned long recNum, recSize;
					if (hashTable->getEntry(i,&recNum,&recSize)) {
						if (recNum == oldBlockNumber) {
							recSize *=2;
						}
						hashTable->editEntry(i,recNum,recSize);
					}
				}


				blockNumber = newBlockNumber;

				stringstream ss;
				ss.clear();
				char convertedDec[32];
				sprintf(convertedDec,"%lu",hashSize);
				ss << "Reutilizando entrada con tamaño de dispersión:";
				ss << hashSize;
				ss << " menor a tamaño de la tabla: ";
				sprintf(convertedDec,"%lu",hashTable->getTableSize());
				ss << convertedDec;
				log->info("table",ss.str());
			}
			else {
				logTableSize();
				logHashTable();

				/// duplico mi tabla
				if (hashTable->increaseHashTableByFactor2() == false) {
					cerr << "<<ERROR>> No se pudo duplicar la tabla de hash" << endl;
				}
				else {
					log->info("table","Duplicando HashTable");

					//TODO: Preguntar a Lampro sobre como hacemos esto!!!
					bitsUsedInHash++;
					// guardar en el archivo la cantidad de bits que usamos
					_file.seekp(0,ios::beg);
					_file.write(reinterpret_cast<char*>(&bitsUsedInHash), sizeof(unsigned long));
					_file.flush();

					hashSize*=2;

					// obtengo un nuevo bloque

					hashTable->editEntry(hash,oldBlockNumber,hashSize);
					unsigned hash2 = hash;

					hash2 ^= (1 << (bitsUsedInHash-1));
					hash2 &= (1<<bitsUsedInHash) -1;

					if (hashRecords->getNewBlock(&newBlockNumber) == false) {
						cout << "<<ERROR>> Error, no se pudo obtener un nuevo bloque" << endl;
					}

					// obtener el numero del nuevo bloque
					hashTable->editEntry(hash2,newBlockNumber,hashSize);
				}
			}

			/// re acomodo los registros viejos
			Block*block = NULL;
			if (hashRecords->getCopyOfBlock(oldBlockNumber,&block) == false) {
				cerr << "<<ERROR>> Error, no se pudo obtener la copia del bloque" << endl;
				res =  false;
				break;
			}

			//borro los registros del bloque viejo
			if (hashRecords->removeAllRecordsFromBlock(oldBlockNumber) == false) {
				cerr << "<<ERROR>> Error, no se pudo vaciar el bloque bloque" << endl;
				res = false;
				break;
			}

			for (unsigned long i = 0; i < block->getRecordCount(); i++) {
				char* record = NULL;
				unsigned long recordSize = -1;
				unsigned long recordKey = -1;

				if (block->getRecord(i,&record,&recordSize) == true) {
					char*p = reinterpret_cast<char*>(&recordKey);
					for (unsigned long j = 0 ; j < sizeof(unsigned long); j++) {
						*p = record[j];
						p++;
					}

					// aplico el hash a recordKey, y guardo en el hashRecord
					unsigned long auxHash = hashFunction(recordKey);
					unsigned long auxBlockNumber,auxHashSize;
					res = getHashTableEntry(auxHash,&auxBlockNumber,&auxHashSize);
					if (res == true) {
						// lo guardamos en el bloque

						char* entity;
						unsigned long longEntity = recordSize - sizeof(unsigned long);

						entity = new char[longEntity];
						memcpy(entity,record+sizeof(unsigned long),longEntity);

						if(hashRecords->addRecord(auxBlockNumber,recordKey,entity,longEntity) == false) {
							cerr << "<<ERROR>> no se pudo agregar el registro" << endl;
						}

						free(entity);
					}
				}
				else {
					cerr << "<<ERROR>> Error, al obtener registro" << endl;
				}
			}


			/// aplico func. de hash
			hash = hashFunction(key);

			/// modifico blockNumber
			res = getHashTableEntry(hash,&blockNumber,&hashSize);
			if (res == false) {
				cerr << "<<ERROR>> No se pudo obtener la entrada en la tabla de hash" << endl;
				res= false;
			}
		}

		logTableSize();
		logHashTable();
		logHashRecords();

		if (res == true) {
			log->info("new","Alta realizada con exito");
		}
		else {
			log->error("new","Error en alta del registro");
			cerr << "ERROR AL AGREGAR REGISTRO" <<endl;
		}
		log->newLine();
	}

// TODO: REMOVER!
	if (res) {
		char* data2 = NULL;
		unsigned long data2_len = 0;
		if (get(key,&data2,&data2_len) == false) {
			cerr << "Se agregó un registro que no pudo ser recuperado. Clave:" << key <<endl;
			cerr << "Block Number: " << blockNumber << endl;
			cerr << "---------------------------------------" << endl;
		}
	}

	return res;
}

bool ExtensibleHash::get(unsigned long key, char** data, unsigned long* data_len) {
	unsigned long hash = hashFunction(key);

	unsigned long blockNumber = 0;
	unsigned long hashSize = 0;

	/* TODO: REMOVER
	if (key == 1112255) {
		cout << "get de clave 1112255" <<endl;
	}
*/
//	cout << "Hash: " << hash << endl;

	bool res = getHashTableEntry(hash,&blockNumber,&hashSize);

	/* TODO: REMOVER
	if (key == 1112255) {
		cout << "HashEntry 1112255:" << res << " - BlockNumber: " << blockNumber <<endl;
	}
*/

	if (res == false)
		return false;

//	cout << "Block Number: " << blockNumber << " key: " << key << endl;

	res = hashRecords->getRecord(blockNumber,key,data,data_len);

	/* TODO: REMOVER
	if (key == 1112255) {
		cout << "getRecord 1112255:" << res << endl;
	}
*/

//	cout << "got record " << res << endl;

	return res;
}

bool ExtensibleHash::has(unsigned long key) {
	unsigned long hash = hashFunction(key);

	unsigned long blockNumber = 0;
	unsigned long hashSize = 0;

	bool found = getHashTableEntry(hash,&blockNumber,&hashSize);
	if (found == false)
		return false;

	// levantar el bloque correspondiente y buscar el registro con clave 'key'
	return hashRecords->getRecordPosition(blockNumber,key) == -1?false:true;
}

/**
 * toma los i bits menos significativos de la clave. donde i es la cantidad de bits que
 * que utilizamos en la tabla
 */
unsigned long ExtensibleHash::hashFunction(unsigned long key) {
	unsigned long mask = (1<<bitsUsedInHash) -1;

	return (key & mask);
}

ExtensibleHash::ExtensibleHash(string xFilePath,unsigned long xBlockSize) {
	log = new Log(xFilePath + ".log");

	filePath = xFilePath;
	blockSize = xBlockSize;
	bitsUsedInHash = 1; // por defecto, el tamaño minimo

	hashTable = new BasicHashTable(xFilePath + "Table");
	hashRecords = new HashRecord(xFilePath + "Records",blockSize);

	stringstream ss (stringstream::in | stringstream::out);

	log->info("init","Hash en archivo: " + xFilePath);
	log->info("init","HasTable en archivo: " + xFilePath + "Table");
	log->info("init","HashRecords en archivo: " + xFilePath + "Records");

	// cargar bitsUsedInHash desde el archivo del hash
	_file.open(filePath.c_str(),ios::out | ios::in | ios::binary);

	if (!_file.is_open()) {
		_file.open(filePath.c_str(), ios::in | ios::out | ios::trunc | ios::binary);
	}
	//TODO: Mejorar usando el logger.
	if (!_file.is_open())
		throw ios_base::failure("cannot create file at \"" + filePath + "\".");


	_file.seekg(0,ios::end);
	if (_file.tellg() >= sizeof(unsigned long)) {
		_file.seekg(0,ios::beg);
		_file.read(reinterpret_cast<char*>(&bitsUsedInHash), sizeof(unsigned long));

		ss.clear();
		ss << "Cargando hash desde archivo. ";
		char convertedDec[32];
		sprintf(convertedDec,"%i",bitsUsedInHash);
		ss <<  convertedDec;
		ss <<" Bit(s) usados en HashTable";

		log->info("load",ss.str());
	}
	else {
		hashTable->setTableSize(2);

		_file.seekp(0,ios::beg);
		_file.write(reinterpret_cast<char*>(&bitsUsedInHash), sizeof(unsigned long));
		_file.flush();

		// creo dos buckets en el archivo de bloques...por defecto usamos un bit
		unsigned long _hash = -1;

		// creo dos entradas en HashRecord, vacias
		unsigned long firstRecordId = 0;
		unsigned long secondRecordId = 1;

		hashRecords->getNewBlock(&firstRecordId);
		hashRecords->getNewBlock(&secondRecordId);

		// guardo esas entradas en la tabla
		bool res = hashTable->addEntry(&_hash,firstRecordId,2);
	//	cout << "Hash: Adding first empty entry: "<< _hash << " :: "<< res <<endl;
		res = hashTable->addEntry(&_hash,secondRecordId,2);
	//	cout << "Hash: Adding second empty entry: "<< _hash << " :: " << res <<endl;

		ss.clear();
		ss << "Creando hash en archivo. ";
		char convertedDec[32];
		sprintf(convertedDec,"%i",bitsUsedInHash);
		ss <<  convertedDec;
		ss << " Bit(s) usados en HashTable";
		log->info("load",ss.str());
	}

	char convertedDec[32];
	sprintf(convertedDec,"%i",bitsUsedInHash);
	//cout << "bits usados: " << convertedDec << endl;

	log->newLine();
}

ExtensibleHash::~ExtensibleHash() {
	delete hashTable;
	delete hashRecords;

	log->close();
	delete log;
}


